package com.zoe.onelink.logging.trace.http.interceptor;

import brave.Span;
import brave.Tracer;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.zoe.onelink.common.constant.OnelinkConstant;
import com.zoe.onelink.common.constant.TraceSpanConstant;
import com.zoe.onelink.logging.trace.cache.SwaggerTraceApiInfoCache;
import com.zoe.onelink.logging.trace.property.TraceLogProperties;
import com.zoe.onelink.logging.trace.util.TraceLogUtil;
import com.zoe.onelink.util.IpUtil;
import com.zoe.onelink.util.OnelinkContextHolder;
import com.zoe.onelink.util.PathMatchUtil;
import com.zoe.onelink.util.SpringUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.util.ContentCachingRequestWrapper;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

/**
 * <p>标题: </p>
 * <p>描述: </p>
 * <p>版权: Copyright (c) 2021</p>
 * <p>公司: 智业软件股份有限公司</p>
 *
 * @version: 1.0
 * @author: ruansheng
 * @date: 2021-08-05
 */
@Slf4j
@RequiredArgsConstructor
public class HttpRequestTraceLogInterceptor implements HandlerInterceptor {


    private final TraceLogProperties traceLogProperties;

    private final Tracer tracer;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        try {
            this.traceHttp(request);
        } catch (Exception e) {
            log.warn("[链路日志] 记录HTTP请求日志失败: {}", e.getMessage());
        }
        return true;
    }

    /**
     * 记录HTTP请求日志
     */
    private void traceHttp(HttpServletRequest request) {
        String traceId = this.tracer.currentSpan().context().traceIdString();
        OnelinkContextHolder.putValue(OnelinkConstant.TRACE_ID, traceId);
        // 获取当前节点Span
        Span span = this.tracer.currentSpan();
        // 设置日志类型
        span.tag(TraceSpanConstant.LOG_TYPE, TraceSpanConstant.LogType.HTTP);
        String requestUri = StrUtil.isBlank(request.getRequestURI()) ? request.getServletPath() : request.getRequestURI();
        String methodName = request.getMethod().toUpperCase(Locale.ROOT);
        String apiSummary = StrUtil.nullToEmpty(SwaggerTraceApiInfoCache.getApiSummary(methodName, requestUri));
        String spanName = StrUtil.format("[HTTP] {} {}", requestUri, requestUri.endsWith(apiSummary) ? "" : apiSummary);
        span.name(spanName);
        TraceLogProperties.HttpLogProperties httpLogProperties = this.traceLogProperties.getHttp();
        if (!PathMatchUtil.isPermit(requestUri, httpLogProperties.getOnlyUrls(), httpLogProperties.getExcludeUrls(),
                true, true, "HTTP_TRACE")) {
            return;
        }
        String contentType = request.getContentType();
        // 获取请求参数,RequestBody参数由TraceRequestBodyAdvice处理
        if (!SpringUtil.isApplicationJson(contentType)) {
            Map<String, String> paramMap = this.flattenReqParamMap(request.getParameterMap());
            String httpReqLog = TraceLogUtil.trim(JSON.toJSONString(paramMap), httpLogProperties.getTrimType(), httpLogProperties.getParamMaxLength());
            span.tag(TraceSpanConstant.HTTP_PARAM, httpReqLog);
        }

        // 获取请求头
        Map<String, String> headerMap = getHttpRequestHeaderMap(request);
        String httpHeaderLog = TraceLogUtil.trim(JSON.toJSONString(headerMap), httpLogProperties.getTrimType(), httpLogProperties.getHeaderMaxLength());
        span.tag(TraceSpanConstant.HTTP_HEADER, httpHeaderLog);
        span.tag(TraceSpanConstant.HTTP_CLIENT_IP, StrUtil.nullToEmpty(IpUtil.getIpAddr(request)));
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        try {
            Span span = this.tracer.currentSpan();
            // 获取RequestBody请求参数 , 为了提高性能,对RequestBody流进行了缓存,所以需要在处理完请求之后再取
            if (SpringUtil.isApplicationJson(request.getContentType()) && request instanceof ContentCachingRequestWrapper) {
                String requestBodyStr = this.getRequestBodyStr((ContentCachingRequestWrapper) request);
                span.tag(TraceSpanConstant.HTTP_PARAM, StrUtil.nullToEmpty(requestBodyStr));
            }
            span.finish();
        } catch (Exception e) {
            log.warn("[链路日志] 记录HTTP请求日志失败: {}", e.getMessage());
        }
    }

    /**
     * 获取请求头
     */
    private Map<String, String> getHttpRequestHeaderMap(HttpServletRequest request) {
        TraceLogProperties.HttpLogProperties requestProperties = this.traceLogProperties.getHttp();
        Enumeration<String> headerNames = request.getHeaderNames();
        Map<String, String> headerMap = new HashMap<>(8);
        boolean include = requestProperties.getIncludeHeaders().size() > 0;
        while (headerNames.hasMoreElements()) {
            String name = headerNames.nextElement();
            if (include ? requestProperties.getIncludeHeaders().contains(name) : !requestProperties.getExcludeHeaders().contains(name)) {
                headerMap.put(name, request.getHeader(name));
            }
        }
        return headerMap;
    }

    /**
     * 扁平化请求参数
     */
    public Map<String, String> flattenReqParamMap(Map<String, String[]> paramMap) {
        if (MapUtil.isEmpty(paramMap)) {
            return Collections.emptyMap();
        }
        Map<String, String> flatMap = new HashMap<>(paramMap.size());
        for (Map.Entry<String, String[]> entry : paramMap.entrySet()) {
            String key = entry.getKey();
            String[] value = entry.getValue();
            String flatVal;
            if (value == null) {
                flatVal = null;
            } else if (value.length == 1) {
                flatVal = value[0];
            } else {
                flatVal = JSON.toJSONString(value);
            }
            flatMap.put(key, flatVal);
        }
        return flatMap;
    }

    /**
     * 获取RequestBody
     */
    private String getRequestBodyStr(ContentCachingRequestWrapper request) {
        String reqBodyStr = null;
        byte[] requestBodyBytes = request.getContentAsByteArray();
        if (requestBodyBytes.length > 0) {
            try {
                reqBodyStr = new String(requestBodyBytes, 0, requestBodyBytes.length, request.getCharacterEncoding());
            } catch (Exception e) {
                // ignore
            }
        }
        return reqBodyStr;
    }

}
